package edu.nyu.hps.assignment9;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Dating {

	int sizeOfAttributes = 0, iterations = 0, MAX_ITERATIONS=10000;
	Double eta = 0.0001, gradient = 10000.0, prevGradient = 10000.0;
	List<Candidate> candidates = new ArrayList<Candidate>();
	Double[] weights, prevWeights;

	public Dating(int sizeOfAttributes) {
		this.sizeOfAttributes = sizeOfAttributes;
		weights = new Double[sizeOfAttributes];
		prevWeights = new Double[sizeOfAttributes];
		for (int i = 0; i < weights.length; i++) {
			// weights[i] = (double) (((int) (Math.random()
			// * (Math.random() > 0.5 ? -1 : 1) * 100))) / 100;
			weights[i] = 0.0;
			prevWeights[i] = 0.0;
		}
	}

	/**
	 * This method set the personal gradient for each candidate depending on the
	 * current set of weights
	 * 
	 * @param c
	 */
	void setPersonalGradient(Candidate c) {
		double ans = 0.0;
		for (int i = 0; i < sizeOfAttributes; i++) {
			ans += weights[i] * c.attributes[i];
		}
		c.personalGradient = ans - c.score;
	}

	/**
	 * This method updates the weights from the given candidate data
	 */
	void setWeights(List<Candidate> arrCandidates) {
		while (iterations <= MAX_ITERATIONS) {
			double localEta;
//			if(prevGradient < gradient){
//				localEta = eta;
//			}
//			else{
//				System.out.println("Called setEta()");
				localEta = setEta();
//				eta = localEta;
//				prevGradient = gradient;
//			}
			gradient = 0.0;
			// Saving the previous weights
			Double[] gt = new Double[sizeOfAttributes];
			for (int i = 0; i < sizeOfAttributes; i++) {
				prevWeights[i] = weights[i];
				gt[i] = 0.0;
			}
			// Refining the gradient to be better
			for (int i = 0; i < arrCandidates.size(); i++) {
				Candidate c = arrCandidates.get(i);

				setPersonalGradient(c);

				// Refining the weights
				for (int j = 0; j < sizeOfAttributes; j++) {
					gt[j] = c.personalGradient * c.attributes[j];
					weights[j] = weights[j] - localEta * gt[j];
					gradient += localEta * gt[j];
				}
//				for (int j = 0; j < sizeOfAttributes; j++) {
//							/ candidates.size();
//				}
			}
			iterations++;
		}
	}

	void calculateWeightsForEta(List<Candidate> arrCandidates, double myEta){
		Double etaWeights[] = new Double[sizeOfAttributes];
		for(int i=0;i<sizeOfAttributes;i++){
			etaWeights[i] = weights[i];
		}
		
		int etaIterations = 0;
		while(etaIterations<10){
			Double[] gt = new Double[sizeOfAttributes];
			for (int i = 0; i < sizeOfAttributes; i++) {
				gt[i] = 0.0;
			}
			
			for (int i = 0; i < arrCandidates.size()-2; i++) {
				Candidate c = arrCandidates.get(i);
				c.personalGradient = 0.0;
				for (int j = 0; j < sizeOfAttributes; j++) {
					c.personalGradient += etaWeights[i] * c.attributes[i];
				}
				c.personalGradient -= c.score;
	
				// Refining the weights
				for (int j = 0; j < sizeOfAttributes; j++) {
					gt[j] = c.personalGradient * c.attributes[j];
					etaWeights[j] = etaWeights[j] - myEta * gt[j];
				}
				etaIterations++;
			}
		}
		for(int i=arrCandidates.size()-2 ;i< arrCandidates.size();i++){
			Candidate c = candidates.get(i);
			for (int j = 0; j < sizeOfAttributes; j++) {
				c.personalGradient += etaWeights[i] * c.attributes[i];
			}
			c.personalGradient -= c.score;
		}
	}
	
	double setEta() {
		double bestCost = 10000.0, bestEta = 0.0;
		for (double i = 0.000001;i<=0.001;i*=10) {
			double currCost = 0.0;
			calculateWeightsForEta(candidates, i);
			for (int j = candidates.size()-2; j < candidates.size(); j++) {
				Candidate c = candidates.get(j);
				currCost += c.personalGradient;
			}

			if (currCost < bestCost) {
				bestCost = currCost;
				bestEta = i;
			}
		}
		System.out.println("Best Eta = " + bestEta);
		return bestEta;
	}
	
	/**
	 * Returns the new generated candidate
	 */
	Candidate getNewCandidates() {
		if(candidates.size()>21){
			if(candidates.get(candidates.size()-1).score < candidates.get(candidates.size()-2).score){
				for(int i=0;i<sizeOfAttributes;i++){
					weights[i] = prevWeights[i];
				}
			}
		}
		iterations = 0;
		setWeights(candidates);
		MAX_ITERATIONS = 1000; 
			
		Candidate c = new Candidate(sizeOfAttributes);
		c.attributes = new Double[100];
		for (int i = 0; i < sizeOfAttributes; i++) {
			if (weights[i] > 0.0) {
				c.attributes[i] = 1.0;
			} else {
				c.attributes[i] = 0.0;
			}
		}
		double score = 0.0;
		for (int i = 0; i < sizeOfAttributes; i++) {
			score += weights[i] * c.attributes[i];
		}
		System.out.println("My score of the generated candidate is: " + score);
		c.setScore(score);
		// myCandidates.add(c);
		candidates.add(c);
		return c;
	}
}